﻿using System;
using System.Collections.Generic;
using System.Numerics;
using SpiceNet.Circuits;
using SpiceNet.Diagnostics;
using SpiceNet.Parameters;
using SpiceNet.Simulations;

namespace SpiceNet.Models
{
    /// <summary>
    /// An instance of a current source
    /// </summary>
    public class CurrentSource : Instance
    {
        /// <summary>
        /// Get parameters
        /// </summary>
        public override Dictionary<string, IP> ParamTable =>_pTable;

        /// <summary>
        /// Private variables
        /// </summary>
        private Complex ISRCac;
        private int ISRCposNode, ISRCnegNode;

        #region Parameters
        private enum _c
        {
            ISRC_DC = 1,
            ISRC_AC_MAG = 2,
            ISRC_AC_PHASE = 3,
            ISRC_AC = 4,
            /* ISRC_PULSE = 5,
            ISRC_SINE = 6,
            ISRC_EXP = 7,
            ISRC_PWL = 8,
            ISRC_SFFM = 9, */
            ISRC_WAVEFORM = 5, // We're working with waveforms
            ISRC_NEG_NODE = 10,
            ISRC_POS_NODE = 11,
            ISRC_AC_REAL = 12,
            ISRC_AC_IMAG = 13,
            ISRC_FCN_TYPE = 14,
            ISRC_FCN_ORDER = 15,
            ISRC_FCN_COEFFS = 16,
            ISRC_POWER = 17,
            ISRC_D_F1 = 18,
            ISRC_D_F2 = 19,
            ISRC_VOLTS = 20,
        }
        private static Dictionary<string, IP> _pTable = new Dictionary<string, IP>
        {
            { "dc", new IP(IF.IOPP, (int)_c.ISRC_DC, T.REAL, "DC value of source") },
            { "acmag", new IP(IF.IOPPA, (int)_c.ISRC_AC_MAG, T.REAL, "AC magnitude") },
            { "acphase", new IP(IF.IOPAAU, (int)_c.ISRC_AC_PHASE, T.REAL, "AC phase") },
            /* { "pulse", new IP(IF.IP, (int)_c.ISRC_PULSE, T.REALVEC, "Pulse description") },
            { "sine", new IP(IF.IP, (int)_c.ISRC_SINE, T.REALVEC, "Sinusoidal source description") },
            { "sin", new IP(IF.IP, (int)_c.ISRC_SINE, T.REALVEC, "Sinusoidal source description") },
            { "exp", new IP(IF.IP, (int)_c.ISRC_EXP, T.REALVEC, "Exponential source description") },
            { "pwl", new IP(IF.IP, (int)_c.ISRC_PWL, T.REALVEC, "Piecewise linear description") },
            { "sffm", new IP(IF.IP, (int)_c.ISRC_SFFM, T.REALVEC, "single freq. FM description") }, */
            { "waveform", new IP(IF.IOP, (int)_c.ISRC_WAVEFORM, T.WAVEFORM, "The waveform description") },
            { "neg_node", new IP(IF.OPU, (int)_c.ISRC_NEG_NODE, T.INTEGER, "Negative node of source") },
            { "pos_node", new IP(IF.OPU, (int)_c.ISRC_POS_NODE, T.INTEGER, "Positive node of source") },
            { "acreal", new IP(IF.OPU, (int)_c.ISRC_AC_REAL, T.REAL, "AC real part") },
            { "acimag", new IP(IF.OPU, (int)_c.ISRC_AC_IMAG, T.REAL, "AC imaginary part") },
            { "function", new IP(IF.OPU, (int)_c.ISRC_FCN_TYPE, T.INTEGER, "Function of the source") },
            { "order", new IP(IF.OPU, (int)_c.ISRC_FCN_ORDER, T.INTEGER, "Order of the source function") },
            { "coeffs", new IP(IF.OPU, (int)_c.ISRC_FCN_COEFFS, T.REALVEC, "Coefficients of the source") },
            { "v", new IP(IF.OP, (int)_c.ISRC_VOLTS, T.REAL, "Voltage across the supply") },
            { "p", new IP(IF.OP, (int)_c.ISRC_POWER, T.REAL, "Power supplied by the source") },
            { "ac", new IP(IF.IP, (int)_c.ISRC_AC, T.REALVEC, "AC magnitude,phase vector") },
            { "c", new IP(IF.IP, (int)_c.ISRC_DC, T.REAL, "Current through current source") },
            { "distof1", new IP(IF.IP, (int)_c.ISRC_D_F1, T.REALVEC, "f1 input for distortion") },
            { "distof2", new IP(IF.IP, (int)_c.ISRC_D_F2, T.REALVEC, "f2 input for distortion") }
        };

        public Parameter<Waveform> ISRCWaveform { get; } = new Parameter<Waveform>();
        public Parameter<double> ISRCdcValue { get; } = new Parameter<double>();
        public Parameter<double> ISRCacMag { get; } = new Parameter<double>();
        public Parameter<double> ISRCacPhase { get; } = new Parameter<double>();
        public Parameter<double> ISRCdF1mag { get; } = new Parameter<double>();
        public Parameter<double> ISRCdF2mag { get; } = new Parameter<double>();
        public Parameter<double> ISRCdF1phase { get; } = new Parameter<double>();
        public Parameter<double> ISRCdF2phase { get; } = new Parameter<double>();
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name"></param>
        public CurrentSource(string name) : base(name, 2, typeof(CurrentSource)) { }

        /// <summary>
        /// Set parameter
        /// </summary>
        protected override void Param(int id, object value, Circuit ckt = null)
        {
            double[] v;
            switch ((_c)id)
            {
                case _c.ISRC_DC:
                    ISRCdcValue.Par((double)value);
                    break;
                case _c.ISRC_AC_MAG:
                    ISRCacMag.Par((double)value);
                    break;
                case _c.ISRC_AC_PHASE:
                    ISRCacPhase.Par((double)value);
                    break;
                case _c.ISRC_AC:
                    v = (double[])value;
                    switch (v.Length)
                    {
                        case 2:
                            ISRCacPhase.Par(v[1]); goto case 1;
                        case 1:
                            ISRCacMag.Par(v[0]); break;
                        case 0:
                            ISRCacMag.Par(ISRCacMag.Value);
                            break;
                        default:
                            throw new BadParameterException();
                    }
                    break;
                case _c.ISRC_WAVEFORM:
                    ISRCWaveform.Par((Waveform)value);
                    break;
                case _c.ISRC_D_F1:
                    v = (double[])value;
                    switch (v.Length)
                    {
                        case 2:
                            ISRCdF1phase.Par(v[1]);
                            ISRCdF1mag.Par(v[0]);
                            break;
                        case 1:
                            ISRCdF1mag.Par(v[0]);
                            ISRCdF1phase.Par(0.0);
                            break;
                        case 0:
                            ISRCdF1mag.Par(1.0);
                            ISRCdF1phase.Par(0.0);
                            break;
                        default:
                            throw new BadParameterException();
                    }
                    break;
                case _c.ISRC_D_F2:
                    v = (double[])value;
                    switch (v.Length)
                    {
                        case 2:
                            ISRCdF2phase.Par(v[1]);
                            ISRCdF2mag.Par(v[0]);
                            break;
                        case 1:
                            ISRCdF2mag.Par(v[0]);
                            ISRCdF2phase.Par(0.0);
                            break;
                        case 0:
                            ISRCdF2mag.Par(1.0);
                            ISRCdF2phase.Par(0.0);
                            break;
                        default:
                            throw new BadParameterException();
                    }
                    break;
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Ask parameter
        /// </summary>
        protected override object Ask(int id, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.ISRC_DC:
                    return ISRCdcValue.Value;
                case _c.ISRC_AC_MAG:
                    return ISRCacMag.Value;
                case _c.ISRC_AC_PHASE:
                    return ISRCacPhase.Value;
                case _c.ISRC_WAVEFORM:
                    return ISRCWaveform.Value;
                case _c.ISRC_NEG_NODE:
                    return ISRCnegNode;
                case _c.ISRC_POS_NODE:
                    return ISRCposNode;
                case _c.ISRC_AC_REAL:
                    return ISRCac.Real;
                case _c.ISRC_AC_IMAG:
                    return ISRCac.Imaginary;
                case _c.ISRC_VOLTS:
                    return (ckt.State.Solution[ISRCposNode] -
                    ckt.State.Solution[ISRCnegNode]);
                case _c.ISRC_POWER:
                    if (ckt.CurrentAnalysis.HasFlag(AnalysisType.DoingAc))
                    {
                        throw new AcCurrentPowerException();
                    }
                    else
                    {
                        return -ISRCdcValue *
                                (ckt.State.Solution[ISRCposNode] -
                                ckt.State.Solution[ISRCnegNode]);
                    }
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">The name of the current source</param>
        /// <param name="posnode">The name of the positive node</param>
        /// <param name="negnode">The name of the negative node</param>
        /// <param name="dc">The DC value of the current source</param>
        public CurrentSource(string name, string posnode, string negnode, double dc) : this(name)
        {
            Connect(posnode, negnode);
            ISRCdcValue.Par(dc);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">The name of the current source</param>
        /// <param name="posnode">The name of the positive node</param>
        /// <param name="negnode">The name of the negative node</param>
        /// <param name="w">The waveform of the current in time</param>
        public CurrentSource(string name, string posnode, string negnode, Waveform w)
            : this(name)
        {
            this.Connect(posnode, negnode);
            ISRCWaveform.Par(w);
        }

        /// <summary>
        /// Setup the current source instance
        /// </summary>
        /// <param name="ckt"></param>
        public override void Setup(Circuit ckt, Model model, object p = null)
        {
            // Bind the nodes of the current source
            var nodes = BindNodes(ckt);
            ISRCposNode = nodes[0].Number;
            ISRCnegNode = nodes[1].Number;

            // Setup the waveform if given
            if (ISRCWaveform.Value != null)
                ISRCWaveform.Value.Setup(this, ckt);
        }

        /// <summary>
        /// Do temperature dependent calculations
        /// </summary>
        /// <param name="ckt"></param>
        public override void Temperature(Circuit ckt, Model model, object p = null)
        {
            // [EDIT] Do not touch ac, acPhase, acMag because they are handled in the Parameter class
            /* if(ISRCac.Given && !ISRCacM.Given) {
                ISRCacMag=1;
            }
            if(ISRCac.Given && !ISRCacP.Given) {
                ISRCacPhase=0;
            } */

            if (!ISRCdcValue.Given)
            {
                /* no DC value - either have a transient value, or none */
                if (ISRCWaveform.Given)
                    CircuitWarning.Warning(this, string.Format("Source {0} has no DC value, transient time 0 value used", Name));
                else
                    CircuitWarning.Warning(this, string.Format("Source {0} has no value, DC 0 assumed", Name));
            }
            double radians = ISRCacPhase * Circuit.CONSTPI / 180.0;
            ISRCac = new Complex(ISRCacMag * Math.Cos(radians), ISRCacMag * Math.Sin(radians));
        }

        /// <summary>
        /// Load the current source in the circuit
        /// </summary>
        /// <param name="ckt"></param>
        public override void Load(Circuit ckt, Model model, object p = null)
        {
            CircuitState state = ckt.State;

            double value = 0.0;
            double time = 0.0;
            if ((ckt.Mode & (Circuit.Modes.DcOp | Circuit.Modes.DcTranCurve)) != 0)
            {
                /* load using DC value */
                value = ISRCdcValue * ckt.SrcFact;
            }
            else
            {
                if (ckt.Mode.HasFlag(Circuit.Modes.Dc))
                    time = 0.0;
                else
                    time = state.Time;

                /* use transient function */
                if (ISRCWaveform.Value != null)
                    value = ISRCWaveform.Value.At(time); // Takes care of whole switch statement
                else
                    value = ISRCdcValue * ckt.SrcFact; // Default
            }

            // loadDone:
            state.Rhs[ISRCposNode] += value;
            state.Rhs[ISRCnegNode] -= value;
        }

        /// <summary>
        /// Load the current source in the circuit for AC analysis
        /// </summary>
        /// <param name="ckt"></param>
        public override void AcLoad(Circuit ckt, Model model, object p = null)
        {
            CircuitStateComplex state = ckt.StateComplex;
            state.Rhs[ISRCposNode] += ISRCac;
            state.Rhs[ISRCnegNode] -= ISRCac;
        }

        /// <summary>
        /// Accept the current time point
        /// </summary>
        /// <param name="ckt"></param>
        public override void Accept(Circuit ckt, Model model, object p = null)
        {
            if (ISRCWaveform.Given)
                ISRCWaveform.Value?.Accept(ckt);
        }
    }
}
